package com.magenta.maxoptra.geocoding.decarta;

import com.magenta.maxoptra.geocoding.decarta.jaxb.*;
import com.magenta.maxoptra.gis.GeoException;
import com.magenta.maxoptra.gis.GeoRoute;
import com.magenta.maxoptra.gis.GeographicCoordinates;

import org.apache.http.client.HttpClient;

import javax.xml.bind.JAXBElement;
import javax.xml.datatype.Duration;
import java.util.Date;

/**
 * Created by const Date: 25.03.2009 Time: 16:55:34
 *<p/>
 *  Implementation of the deCarta route request.
 *<p/>
 * @author Konstantin Pestrikov
 * @see DecartaWSRequest
 */
public class RouteRequest extends DecartaWSRequest<GeoRoute> {
    private final GeographicCoordinates from;
    private final GeographicCoordinates to;
    private final RoutePreference preference;

    public RouteRequest(HttpClient client, GeographicCoordinates from, GeographicCoordinates to, RoutePreference preference) {
        super(client);
        this.from = from;
        this.to = to;
        this.preference = preference;
    }

    @Override
    protected GeoRoute execute() throws GeoException {
        JAXBElement<PositionType> origin = createPos(from.getLatitude(), from.getLongitude());
        JAXBElement<PositionType> destination = createPos(to.getLatitude(), to.getLongitude());

        //ask only for route instructions. No maps, no route geometry
        JAXBElement<DetermineRouteRequestType> request = formulateRouteInstructionsRequest(origin, destination);

        XLSMessage msg = newXLSMessage(request, "DetermineRouteRequest");

        //post to http server
        XLSMessage resp = dataSource.executeRequest(msg);

        return handleResponse(resp);
    }

    private GeoRoute handleResponse(XLSMessage resp) throws GeoException {
        ResponseType response = (ResponseType) resp.getValue().getBody().get(0).getValue();
        if (response.getNumberOfResponses().intValue() > 0) {
            DetermineRouteResponseType rParams = (DetermineRouteResponseType) response.getResponseParameters().getValue();
            RouteSummaryType summary = rParams.getRouteSummary();

            DistanceType distance = summary.getTotalDistance();
            Duration xmlDuration = summary.getTotalTime();

            long duration = xmlDuration.getTimeInMillis(new Date());
            double dist = getMiles(distance);

            return new GeoRoute(dist, duration, from, to);
        } else {
            return handleErrors(response, getClass());
        }
    }

    private static final double METERS_IN_MILE = 1609.344;
    private static final double METERS_IN_FOOT = 0.3048;
    private static final double METERS_IN_YARD = 0.9144;

    private double getMiles(DistanceType distance) {
        double dist = distance.getValue().doubleValue();
        switch (distance.getUom()) { // check Units Of Measure
            case MI: break;
            case KM:
                dist = dist / METERS_IN_MILE / 1000;
                break;
            case M:
                dist = dist / METERS_IN_MILE;
                break;
            case DM:
                dist = dist / METERS_IN_MILE * 100;
                break;
            case FT:
                dist = dist / METERS_IN_FOOT / METERS_IN_MILE;
                break;
            case YD:
                dist = dist / METERS_IN_YARD / METERS_IN_MILE;
                break;
        }
        return dist;
    }

    JAXBElement<DetermineRouteRequestType> formulateRouteInstructionsRequest(JAXBElement<PositionType> origin, JAXBElement<PositionType> dest) {
        DetermineRouteRequestType routeReq = factory.createDetermineRouteRequestType();
        RoutePlanType routePlan = factory.createRoutePlanType();
        routeReq.setRoutePlan(routePlan);
        routeReq.setProvideRouteHandle(false);
        routePlan.setRoutePreference(preference.getValue());
        WayPointType start = factory.createWayPointType();
        start.setLocation(origin);
        WayPointType end = factory.createWayPointType();
        end.setLocation(dest);
        WayPointListType waypoints = factory.createWayPointListType();
        waypoints.setStartPoint(start);
        waypoints.setEndPoint(end);
        routePlan.setWayPointList(waypoints);

        RouteInstructionsRequestType routeInstructions = factory.createRouteInstructionsRequestType();
        routeReq.setRouteInstructionsRequest(routeInstructions);
        return factory.createDetermineRouteRequest(routeReq);
    }

    protected enum RoutePreference {
        FASTEST("Fastest"),
        SHORTEST("Shortest");
        private final String value;
        RoutePreference(String value) {
            this.value = value;
        }
        public String getValue() {
            return value;
        }
    }
}
